#ifndef __EVENTLOOP_H__
#define __EVENTLOOP_H__

#include "Spark/Utils/MPSCQueue.h"
#include "Spark/Utils/TimePoint.h"
#include "Spark/Net/CallbackDefs.h"
#include <memory>
#include <list>
#include <thread>

class Poller;
class Channel;
class TimerQueue;
using ChannelList = std::list<Channel*>;

class EventLoop
{
  public:
    EventLoop();
    ~EventLoop();

    /**
     * @brief Check if EventLoop is in it's own Thread
     * @return true
     * @return false
     */
    bool isInLoopThread() const;

    /**
     * @brief Assertion to make sure the EventLoop is running in it's own Thread
     */
    void assertInLoopThread();

    /**
     * @brief Move the EventLoop to current Thread,
     * must be called before looping
     */
    void moveToCurrentThread();

    /**
     * @brief Run task in loop. Note that if is currently handling tasks
     * , the task will be queued in taskQueue_
     * @param  task Functor to run
     */
    void runInLoop(Task task);

    /**
     * @brief Queue the task in taskQueue_
     * @param  task Functor to queue
     */
    void queueInLoop(Task task);

    /**
     * @brief Run task at given TimePoint
     * @param  when When to run the Task
     * @param  task Functor
     * @return TimerID
     */
    TimerID runAt(TimePoint when, Task task);

    /**
     * @brief Run task after delayed std::chrono::duration
     * @tparam P Precision, could be double, long
     * @tparam R Reps, couldb be 1 for seconds, 1000 for millis, etc.
     * @param  delay Given delayed duration
     * @param  task Functor
     * @return TimerID
     */
    template <class P, class R = void>
    TimerID runAfter(std::chrono::duration<P, R> delay, Task task);

    /**
     * @brief Run task in given Interval
     * @tparam P Precision, could be double, long
     * @tparam R Reps, couldb be 1 for seconds, 1000 for millis, etc.
     * @param  interval could be any valid std::chrono::duration<P, R>
     * When P == long,  R could be std::ratio<1l, 1l> std::milli, std::micro,
     * std::nano; or if P == double, R can only be std::ratio<1l, 1l>
     * @param  task XXX
     * @return TimerID
     */
    template <class P, class R>
    TimerID runEvery(TimeInterval<P, R> interval, Task task);

    /**
     * @brief Disable timer with given TimerID
     * @param  id Timer to disable
     */
    void disableTimer(TimerID id);

    /**
     * @brief Check if EventLoop is running in its loop
     * @return true
     * @return false
     */
    bool isRunning() const
    {
        return looping_ && !quit_;
    }

    /**
     * @brief Quit EventLoop
     */
    void quit();

    /**
     * @brief Start looping
     */
    void loop();

    /**
     * @brief Get the Event Loop Of This Thread object
     * @return EventLoop*
     */
    static EventLoop* getEventLoopOfThisThread();

    /**
     * @brief Update given ch in Poller.
     * This function works as a bridge from Channel to Poller
     * @param  ch Pointer to Channel
     */
    void updateChannel(Channel* ch);

    /**
     * @brief Remove given Channel from Poller.
     * This function works as a bridge from Channel to Poller
     * @param  ch Pointer to Channel
     */
    void removeChannel(Channel* ch);

  private:
    /**
     * @brief Wake the EventLoop from epoll_wait
     */
    void wakeup();

    /**
     * @brief To reset timerfd
     */
    void wakeupRead();

    /**
     * @brief Run all the pending functors in taskQueue_ in the end of every
     * loop
     */
    void doPendingTasks();

    /**
     * @brief Reset timer queue
     */
    void resetTimerQueue();

  private:
    bool looping_{false};
    std::atomic_bool quit_{false};
    const std::thread::id threadID_;
    std::unique_ptr<Poller> poller_;
    std::unique_ptr<TimerQueue> timerQueue_;
    ChannelList activeChannels_;
    Channel* currentHandlingChannel_{nullptr};
    MPSCQueue<Task> taskQueue_;
    int wakeupFD_;
    std::unique_ptr<Channel> wakeupChannel_;
    bool callingFunctors_{false};
    EventLoop** localLoop_;
};

#endif  // __EVENTLOOP_H__